<?php
/**
 * SleekRouter 0.1
 *
 * A lightweight router. Despite the name it's not tied to SleekPHP.
 *
 * SleekRouter is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * SleekRouter is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * @author Andreas Lagerkvist <me@andreaslagerkvist.com>
 * @copyright Copyright (c) 2010-2011 Andreas Lagerkvist
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 * @version 0.1
 */
class SleekRouter {
	private $pages;
	private $defaultPage;
	private $params;
	private $routes;
	private $webroot;
	private $modRew;

	/**
	 * Sets up the router and sorts the $routes
	 *
	 * @param array $routes
	 * @param string $webroot
	 * @param bool $modRew
	 * @param string $defaultPage
	 */
	public function __construct (array $routes, $webroot = '/', $modRew = false, $defaultPage = 'Home') {
		$this->routes		= $routes;
		$this->webroot		= $webroot;
		$this->modRew		= $modRew ? true : false;
		$this->defaultPage	= $defaultPage;

		$this->sortRoutes();
	}

	/**
	 * Returns the URL to $requestedPage based on potential $data
	 *
	 * @param string $requestedPage
	 * @param array|bool $data
	 */
	public function urlFor ($requestedPage, $data = false) {
		$url = false;

		foreach ($this->routes as $route => $page) {
			if ($page == $requestedPage) {
				# Remove any possile regexp (TODO: Maybe should check that passed in attr matches regexp - else invalid and continues looking (that way more than one type of URL can point to the same page))
				$route = preg_replace('/\(.*?\)/', '', $route);

				# Grab all the 'dirs' in this url. /archives/$name/ => [archives, $name]
				$dirsInRoute	= explode('/', $route);
				$dirsInURL		= array();

				# Go through every dir in the URL and replace the variable ones with the passed in args
				foreach ($dirsInRoute as $dirInRoute) {
					if (substr($dirInRoute, 0, 1) == '$') {
						$varInRoute = substr($dirInRoute, 1);

						if ($data and isset($data[$varInRoute])) {
							$dirInRoute = $data[$varInRoute];
						}
					}

					$dirsInURL[] = $dirInRoute;
				}

				$url = implode('/', $dirsInURL);

				break;
			}
		}

		$webroot	= $this->modRew ? $this->webroot : ($route == '/' ? $this->webroot : $this->webroot . 'index.php/');
		$url		= $url ? str_replace('//', '/', $webroot . $url) : '#';

		return $url;
	}

	/**
	 * Returns the page name for the $request passed in
	 *
	 * @param string $request
	 */
	public function getPage ($request) {
		if (isset($this->pages[$request])) {
			return $this->pages[$request];
		}

		$this->getParamsFromRequest($request);

		return $this->pages[$request];
	}

	/**
	 * Returns the params for the $request passed in
	 *
	 * @param string $request
	 */
	public function getParams ($request) {
		if (isset($this->params[$request])) {
			return $this->params[$request];
		}

		$this->getParamsFromRequest($request);

		return $this->params[$request];
	}

	/**
	 * Analyses the request and tries to match it to a route stored in $this->routes
	 *
	 * @param string $request
	 **/
	private function getParamsFromRequest ($request) {
		$routes			= $this->routes;			# All loaded routes
		$requested		= explode('/', $request);	# Each requested dir ('/archives/design/' => [archives, design])
		$countRequested	= count($requested);		# Number of requested dirs
		$params			= array();					# For storing the params retrieved from the URI
		$isValid		= false;					# Whether the requested URI is valid (matches a route)

		# Go through each route (/$name/ => Page for example)
		foreach ($routes as $route => $page) {
			# Each matched dir (/archives/$name/ => [archives, $name])
			$matched = explode('/', $route);

			# Make sure the requested URI has the same amount of dirs as the route
			# TODO: Should add support for any number of dirs in route: (/product-categories/:category_id[min,max|empty]/)
			if (count($matched) == $countRequested) {
				$isValid = true;

				# Make sure the directories match
				for ($i = 0; $i < $countRequested; $i++) {
					# First remove any regexp
					$matchedDirNoRegexp = preg_replace('/\(.*?\)/', '', $matched[$i]);

					# See if this route-dir contains a file-extension
					if (strstr($matchedDirNoRegexp, '.')) {
						# Make sure the extension matches the route AND the uri
						# I.E. /$name.htm would match /foo.htm but not /foo.png
						if (end(explode('.', $matchedDirNoRegexp)) != end(explode('.', $requested[$i]))) {
							# Otherwise not a valid request
							$isValid = false;
							break;
						}
					}

					# See if this route-dir is a variable (TODO: vars should be allowed anywhere in route and not just one per "dir")
					if ('$' == substr($matched[$i], 0, 1)) {
						# See if this var contains a reg-exp-match
						if (strstr($matched[$i], '(')) {
							# It does, break out the regexp
							$regExp = explode('(', $matched[$i]);
							$regExp = substr($regExp[1], 0, -1);

							# If the requested dir doesn't match it's not a valid request
							if (!preg_match("/^$regExp$/", $requested[$i])) {
								$isValid = false;
								break;
							}
						}
						# Else - no regexp, anything is valid
					}
					# It's a hard-coded value, make sure the two dirs are identical
					elseif ($matched[$i] != $requested[$i]) {
						$isValid = false;
						break;
					}
				}

				# If isValid is still true that means we've
				# successfully matched a URI, set params and break
				if ($isValid) {
					$this->pages[$request] = $page;
					$i = 0;

					# Go through each "dir" in the route
					# (I.E. foreach /archives/$year/$month/)
					foreach ($matched as $m) {
						# If it's a variable dir (like $year)
						if('$' == substr($m, 0, 1)) {
							# Remove $
							$m = substr($m, 1);

							# Remove potential regexp
							$m = preg_replace('/\(.*?\)/', '', $m);

							# Store the same dir in the requested uri
							# (I.E. if we're on $year and the request is /archives/2009/12/
							# we'd store '2009' here
							$val = $requested[$i];

							# Remove potential file-extension from both route AND request
							if(strstr($m, '.')) {
								$tmpExt	= end(explode('.', $m));
								$m		= substr($m, 0, -(strlen($tmpExt) + 1));

								$tmpExt = end(explode('.', $requested[$i]));
								$val	= substr($val, 0, -(strlen($tmpExt) + 1));
							}

							# Set param $year => 2009
							$params[$m] = $val;
						}

						# Increase so that we check next request-dir next time
						$i++;
					}

					break;
				}
			}
		}

		# We couldn't find a valid route
		if (!$isValid) {
			$this->pages[$request] = false;
		}

		# Set params, either still empty array or full of goodies
		$this->params[$request] = $params;
	}

	/**
	 * Sorts the routes based on variable and static ones as well as length
	 */
	private function sortRoutes () {
		$hardCoded	= array();
		$variable	= array();

		foreach ($this->routes as $route => $page) {
			if (strpos($route, '$') === false) {
				$hardCoded[$route] = $page;
			}
			else {
				$variable[$page] = $route;
			}
		}

		uasort($variable, array($this, 'routeSortingCallback'));

		$this->routes = array_merge($hardCoded, array_flip($variable));
	}

	/**
	 * Callback for route sorting
	 *
	 * @param string $a
	 * @param string $b
	 */
	private function routeSortingCallback ($a, $b) {
		$aLen = strlen($a);
		$bLen = strlen($b);

		if ($aLen == $bLen) {
			return 0;
		}
		elseif ($aLen > $bLen) {
			return 1;
		}
		else {
			return -1;
		}
	}
}
/*
if (isset($_GET['test'])) {
	error_reporting(E_ALL);
	ini_set('display_errors', true);

	$routes = array(
		'/'							=> 'Home', 
		'/$name/'					=> 'Page', 
		'/$name/$lang/'				=> 'Page', 
		'/$name/$page_num([0-9])/'	=> 'Page', 
		'/products/$name[]/'		=> 'ProductCategory', 
		'/email{$email_md5}[2]/'	=> 'EmailPage' # kör samma [min,max] som regexp är (kolla upp)
	);
	$router = new SleekRouter($routes);

	echo "<h2>Routes</h2><pre>";
	var_dump($routes);
	echo "</pre>";

	echo "<h2>/ => " . $router->getPage('/') . "</h2><pre>";
	var_dump($router->getParams('/'));
	echo "</pre>";

	echo "<h2>/bar/ => " . $router->getPage('/bar/') . "</h2><pre>";
	var_dump($router->getParams('/bar/'));
	echo "</pre>";

	echo "<h2>/foo/en/ => " . $router->getPage('/foo/en/') . "</h2><pre>";
	var_dump($router->getParams('/foo/en/'));
	echo "</pre>";

	echo "<h2>/baz/1/ => " . $router->getPage('/baz/1/') . "</h2><pre>";
	var_dump($router->getParams('/foo/en/'));
	echo "</pre>";

	echo "<h2>/products/foo/bar/baz/ => " . $router->getPage('/products/foo/bar/baz/') . "</h2><pre>";
	var_dump($router->getParams('/products/foo/bar/baz/'));
	echo "</pre>";

	echo $router->urlFor('Page', array('name' => 'about')) . ' should be /about/<br/>';
	echo $router->urlFor('Page', array('name' => 'about', 'page_num' => 3)) . ' should be /about/3/<br/>';
	echo $router->urlFor('Page', array('name' => 'about', 'lang' => 'en')) . ' should be /about/en/<br/>';
	echo $router->urlFor('ProductCategory', array('name' => 'apple', '__children' => array('name' => 'hardware'))) . ' should be /prodcuts/apple/hardware/<br/>';
	echo $router->urlFor('EmailPage', array('email_md5' => 'md51', '__children' => array('email_md5' => 'md52'))) . ' should be /emailmd51/emailmd52/<br/>';
}*/
